/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package agents;

import com.springrts.ai.AICommand;
import com.springrts.ai.AICommandWrapper;
import com.springrts.ai.AIFloat3;
import com.springrts.ai.command.AttackUnitAICommand;
import com.springrts.ai.command.BuildUnitAICommand;
import com.springrts.ai.command.MoveUnitAICommand;
import com.springrts.ai.command.SendTextMessageAICommand;
import com.springrts.ai.oo.Economy;
import com.springrts.ai.oo.Feature;
import com.springrts.ai.oo.OOAICallback;
import com.springrts.ai.oo.Resource;
import com.springrts.ai.oo.Unit;
import com.springrts.ai.oo.UnitDef;
import com.springrts.ai.oo.WeaponDef;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import visual.GUIcommander;

/**
 *
 * @author xavi
 */
public class MySpringAgent extends com.springrts.ai.oo.AbstractOOAI {
    /**
     * Object through which the agent communicates with the engine
     */
    private OOAICallback callback;

    /**
     * Reference to agent's commander.  The agent gets this reference when
     * <code>unitFinished</code> method is called.
     */
    private Unit commander;

    /**
     * Reference to team of the agent.  The agent gets this reference when
     * <code>init</code> method is called.
     */
    private int teamId;

    /**
     * Resources
     */
    List<Resource> resources;
    private final int METAL = 0;
    private final int ENERGY = 1;

    /**
     * Map information
     */
    private MapInfo mapInfo;

    /**
     * Reference to who will take the following action. The agent gets this
     * reference when <code>move</code>, <code>construct</code>,
     * <code>attack</code> or <code>sendMessage</code> method is called.
     */
    private Unit unitOrigin;
    
    /**
     * Reference to unit that will be the target of the following action of the
     * agent. The agent gets this reference when <code>attack</code> or
     * <code>sendMessage</code> method is called.
     */
    private Unit unitTarget;

    /**
     * Reference to unit definition that agent will construct.  The agent gets 
     * this reference when <code>construct</code> method is called.
     */
    private UnitDef construction;

    /**
     * Message to be sent in the following <code>sendMessage</code> action
     */
    private String message;

    /**
     * Zone where will be sent the following message.
     */
    private int zone;

    /**
     * Point where will be done the following action
     */
    private AIFloat3 point;

    /**
     * Last commander position
     */
    private AIFloat3 lastPosition;
    
    /**
     * Determines what is the following action that the agent will try to fulfil
     * </p>
     * Can take the values:
     * <ul>
     * </li> ERROR    (-1): Error
     * </li> NOTHING   (0): Nothing to do
     * </li> MOVE      (1): Move
     * </li> CONSTRUCT (2): Construct
     * </li> ATTACK    (3): Attack
     * </li> MESSAGE   (4): Send a message
     * </ul>
     * 
     */
    private int behaviour;

    /**
     * Commands handled and not finished.
     * </p>
     * Allows tracking the command during the execution for identify a possible
     * result event, which would come with the same id.
     * </p>
     * Elements are added when <code>handleCommand</code> method is called, and
     * they are removed when <code>commandFinished</code> method is called
     */
    private ArrayList<AICommand> currentCommands;
    /**
     * Values that can take the {@link behaviour} atribure
     */
    public final int ERROR=-1, NOTHING=0, MOVE=1, CONSTRUCT=2, ATTACK=3, 
            MESSAGE=4;

    /**
     * Agent graphic interface
     */
    private GUIcommander guiCommander;

    private PrintStream outfile;


    /**
     * <code>init</code> method is called at the start of every game (right at
     * the end of the countdown)
     * 
     * @param teamId
     * @param callback Object through which the agent communicates with the
     *                 engine
     * @return
     */
    @Override
    public int init(int teamId, OOAICallback callback) {
        this.teamId = teamId;
        this.callback = callback;
        resources = callback.getResources();
        currentCommands = new ArrayList<AICommand>();
        behaviour = NOTHING;

        // Read map information:
        mapInfo = new MapInfo(callback.getMap(), resources.get(METAL));
        //callback.getMap().getHeightMap();

        // Read mod information
        //callback.getMod().get...

        guiCommander = new GUIcommander(this);
        guiCommander.setVisible(true);

        // ---------------
        FileOutputStream Output;
        try
        {
            Output = new FileOutputStream("/home/xavi/Escritorio/out.txt");
            outfile = new PrintStream(Output);
        }
        catch  (Exception e)
        {
            System.out.println("Could not load file!");
        }
        // ---------------
        
        return 0;
    }

    // +++++++++++++++++++++++++     EVENTS     +++++++++++++++++++++++++

    /**
     * UnitFinished event is triggered whenever a unit has been built.
     * <p/>
     * At the start of the game, the engine will send a 'UnitFinished' event for
     * every starting unit. When playing BA, this means you will get a 
     * 'UnitFinished' event for the commander.
     * 
     * @param unit Reference to agent's commander
     * @return
     */
    @Override public int unitFinished(Unit unit) {
        // To get a reference to the commander, is necessary to check if the
        // unit is the command
        if(commander==null){
            if (unit.getDef().getName().equals("armcom")){
                this.commander = unit;
                lastPosition = new AIFloat3(-1,-1,-1);
                guiCommander.refreshTeamUnits(callback.getTeamUnits());
            }
        }
        guiCommander.addEvent("[unitFinished] "+unit.getDef().getHumanName()+
                " (team: "+unit.getTeam()+")");
        return 0;
    }

    /**
     * Is called every time a unit is created, and tells you the unit that was
     * created and the unit that built it (can be null)
     *<p/>
     * Is called when starts the unit building.
     *
     * @param unit
     * @param commander
     * @return
     */
    @Override
    public int unitCreated(Unit unit, Unit commander){
        guiCommander.addEvent("[unitCreated] "+unit.getDef().getHumanName()+
                " (team: "+unit.getTeam()+")");
        guiCommander.refreshTeamUnits(callback.getTeamUnits());
        return 0;
    }

    @Override
   public int unitCaptured(Unit unit, int oldTeamId, int newTeamId){
        guiCommander.addEvent("[unitCaptured] "+unit.getUnitId()+
                " (from team "+oldTeamId+" to"+newTeamId+")");
        return 0;
    }

    @Override
    public int unitDamaged(Unit unit, Unit attacker, float damage, AIFloat3 dir, WeaponDef weaponDef, boolean paralyzer){
        guiCommander.addEvent("[unitDamaged] "+unit.getUnitId()+
                " (attacker: "+attacker.getUnitId()+", damage: "+damage+
                ", dir: "+dir.toString()+", paralyzer: "+paralyzer+")");
        return 0;
    }

    @Override
    public int unitDestroyed(Unit unit, Unit attacker){
        guiCommander.addEvent("[unitDestroyed] "+unit.getUnitId()+
                " (attacker: "+attacker.getUnitId()+")");
        guiCommander.refreshTeamUnits(callback.getTeamUnits());
        return 0;
    }

    @Override
    public int unitMoveFailed(Unit unit){
        guiCommander.addEvent("[unitMoveFailed] "+unit.getUnitId());
        return 0;
    }

    @Override
    public int unitIdle(Unit unit){
        guiCommander.addEvent("[unitIdle] "+unit.getUnitId());
        return 0;
    }

    @Override
    public int unitGiven(Unit unit, int oldTeamId, int newTeamId){
        guiCommander.addEvent("[unitGiven] "+unit.getUnitId()+
                " (from team "+oldTeamId+" to"+newTeamId+")");
        return 0;
    }

    @Override
    public int enemyEnterRadar(Unit enemy){
        String str = "[enemyEnterRadar] "+enemy.getUnitId()+
                " (team: "+enemy.getTeam()+")";
        guiCommander.addEvent(str);
        return 0;
    }

    @Override
    public int enemyLeaveRadar(Unit enemy){
        String str = "[enemyLeaveRadar] "+enemy.getUnitId()+
                " (team: "+enemy.getTeam()+")";
        guiCommander.addEvent(str);
        return 0;
    }

    @Override
    public int enemyEnterLOS(Unit enemy){
        String str = "[enemyEnterLOS] "+enemy.getUnitId()+
                " (team: "+enemy.getTeam()+")";
        guiCommander.addEvent(str);
        return 0;
    }

    @Override
    public int enemyLeaveLOS(Unit enemy){
        String str = "[enemyLeaveLOS] "+enemy.getUnitId()+
                " (team: "+enemy.getTeam()+")";
        guiCommander.addEvent(str);
        return 0;
    }

    @Override
    public int enemyDamaged(Unit enemy, Unit attacker, float damage, AIFloat3 dir, WeaponDef weaponDef, boolean paralyzer){
        guiCommander.addEvent("[enemyDamaged] "+enemy.getUnitId()+
                " (attacker: "+attacker.getUnitId()+", damage: "+damage+
                ", dir: "+dir.toString()+", paralyzer: "+paralyzer+")");
        return 0;
    }

    @Override
    public int enemyDestroyed(Unit enemy, Unit attacker){
        guiCommander.addEvent("[enemyDestroyed] "+enemy.getUnitId()+
                " (attacker: "+attacker.getUnitId()+")");
        return 0;
    }

    @Override
    public int weaponFired(Unit unit, WeaponDef weaponDef){
        guiCommander.addEvent("[weaponFired] "+unit.getUnitId()+
                " (weaponDef: "+weaponDef.getName()+")");
        return 0;
    }

    @Override
    public int seismicPing(AIFloat3 pos, float strength){
        guiCommander.addEvent("[seismicPing] pos:"+pos.toString()+
                ", strength: "+strength);
        return 0;
    }

    @Override
    public int commandFinished(Unit unit, int commandId, int commandTopicId){
        String str_command = "";
//        for(int i=0; i<currentCommands.size(); i++){
//            if(currentCommands.get(i).getTopic()==commandTopicId){
//                currentCommands.remove(i);
//            }
//        }
        guiCommander.addEvent("[commandFinished] unit: "+unit.getUnitId()+
                ", commandId: "+commandId+", commandTopicId: "+commandTopicId);
        return 0;
    }

    @Override
    public int message(int player, String message){
        guiCommander.addEvent("[message] player: "+player+" message: "+message);
        return 0;
    }

    // ++++++++++++++++++++++   EVERY FRAME OF THE GAME   ++++++++++++++++++++++

    /**
     * This method is called at every frame of the game
     *
     * @param frame actual frame of the game
     * @return
     */
    @Override public int update(int frame) {
        outfile.println("[update] frame: "+frame);

        // +++++++++++++++++++   REFRESH VALUES   +++++++++++++++++++

        guiCommander.refreshEnemyLOSandRadar(callback.getEnemyUnits(),
                                        callback.getEnemyUnitsInRadarAndLos());

        if(frame%5==0){
            AIFloat3 c = commander.getPos();
            if(lastPosition.x!=c.x || lastPosition.y!=c.y || lastPosition.z!=c.z){
                try{
                    guiCommander.refreshMines(this.distanceToMetalMapSpots());
                    lastPosition=c;
                }catch(Exception e){
                    outfile.println("message: "+e.getMessage()+"; class: "+e.getClass()+"; Stack: ");
                    StackTraceElement[] l = e.getStackTrace();
                    for(int i=0; i<l.length; i++){
                        outfile.println(l[i]);
                    }
                }
            }

            Economy e = callback.getEconomy();
            Resource r = resources.get(METAL);
            guiCommander.refreshMetalEconomy(r.getName(),e.getCurrent(r),
                    e.getIncome(r), e.getStorage(r), e.getUsage(r));
            r = resources.get(ENERGY);
            guiCommander.refreshEnergyEconomy(r.getName(),e.getCurrent(r),
                    e.getIncome(r), e.getStorage(r), e.getUsage(r));

        }

        // ++++++++++++++++++   CHANGE BEHAVIOUR   ++++++++++++++++++

        switch (behaviour){
            case NOTHING:
                break;
            case MOVE:      this.move();
                break;
            case CONSTRUCT: this.construct();
                break;
            case ATTACK:    this.attack();
                break;
            case MESSAGE:   this.sendMessage();
                break;
             default:
                break;
        }

        return 0;
    }

    // +++++++++++++++++++++++++   PRIVATE METHODS   +++++++++++++++++++++++++

    private void move(){
        int groupID = -1;
        int timeOut = 10000;
        AICommand command = new MoveUnitAICommand(unitOrigin, groupID,
                             new ArrayList<AICommand.Option>(), timeOut, point);
        this.handleCommand(command);
    }

    private void construct(){
        int groupID = -1;
        int timeOut = 10000;
        AICommand command = new BuildUnitAICommand(unitOrigin, groupID,
                    new ArrayList<AICommand.Option>(), timeOut, construction,
                    point, 0);
//            BuildUnitAICommand(
//                    the unit we want to give to order to,
//                    the group we want to give the order to = -1, --> In this example we only have a single unit
//                    a list addional options. See AICommand.Option for details,
//                    the number of frames after which the command times out,
//                    the UnitDef of the unit we want to build,
//                    the position to build the unit at,
//                    the direction the new unit should face)
        this.handleCommand(command);
    }

    private void attack(){
        int groupID = -1;
        int timeOut = 10000;
        AICommand command = new AttackUnitAICommand(unitOrigin, groupID,
                          new ArrayList<AICommand.Option>(), timeOut, unitTarget);
        this.handleCommand(command);
    }

    private void sendMessage(){
        AICommand command = new SendTextMessageAICommand(message, zone);
        this.handleCommand(command);
    }

    /**
     * Give the command to the engine
     */
    private void handleCommand(AICommand command){
        this.callback.getEngine().handleCommand(
                            AICommandWrapper.COMMAND_TO_ID_ENGINE, -1, command);
//            callback.getEngine().handleCommand(
//                id of the recipient of the command,
//                commandID = -1,  --> this is a feature which is not yet supported
//                the command itself)      by spring (asynchronous command tracking)
        guiCommander.addEvent("<handleCommand> commandTopicId: "+command.getTopic());
        currentCommands.add(command);
        behaviour = this.NOTHING;
    }

    private List<AIFloat3> distanceToMetalMapSpots() throws Exception{
        List<AIFloat3> minesList = new ArrayList<AIFloat3>();
        AIFloat3 cp = commander.getPos();
        for(int i=0; i<mapInfo.getNmines(); i++){
            AIFloat3 p = mapInfo.getMetalMapSpotsPosition(i);
            AIFloat3 d = new AIFloat3((p.x-cp.x),(p.y-cp.y),(p.z-cp.z));
            // Sort by proximity
            boolean added =false;
            int j=0;
            while(j<minesList.size() && !added){
                if(lessDistance(d,minesList.get(j))){
                    minesList.add(j, d);
                    added=true;
                }else{
                    j++;
                }
            }
            if(!added){
                minesList.add(d);
            }
        }
        return minesList;
    }
    /**
     * Returns true if the distance from origin (0,0,0) and p1 is less than the
     * distance from origin to p2.
     *
     * @param p1
     * @param p2
     * @return
     */
    private boolean lessDistance(AIFloat3 p1, AIFloat3 p2){
        if(Math.sqrt(Math.pow(p1.x, 2)+Math.pow(p1.y, 2)+Math.pow(p1.z, 2)) <
                Math.sqrt(Math.pow(p2.x, 2)+Math.pow(p2.y, 2)+Math.pow(p2.z, 2))){
            return true;
        }
        else{
            return false;
        }
    }

    // +++++++++++++++++++++++++     API     +++++++++++++++++++++++++

    public void move(Unit unit, float x, float z){
        AIFloat3 commanderPos = this.commander.getPos();
        float y = mapInfo.getElevationAt(x, z);
        point = new AIFloat3(commanderPos.x+x,commanderPos.y+y,
                commanderPos.z+z);//y: down x: right
        unitOrigin=unit;
        behaviour = this.MOVE;
    }

    public void construct(Unit constructor, String construction, float x, float z){
        AIFloat3 commanderPos = this.commander.getPos();
        float y = mapInfo.getElevationAt(x, z);
        point = new AIFloat3(commanderPos.x+x,commanderPos.y+y,
                commanderPos.z+z);//y: down x: right
        unitOrigin=constructor;
        this.construction=this.getUnitDef(construction);
        behaviour = this.CONSTRUCT;
    }

    public void attack(Unit attacker, Unit enemy){
        unitOrigin=attacker;
        unitTarget=enemy;
        behaviour=this.ATTACK;
    }

    public void sendMessage(Unit sender, Unit reciever){
        unitOrigin=sender;
        unitTarget=reciever;
        behaviour=this.MESSAGE;
    }

    public UnitDef getUnitDef(String unitName){
        List<UnitDef> unitDefs = this.callback.getUnitDefs();
        // callback.getUnitDefs()  will give you a list of all the available
        // unit types in the game that is currently being played

        for (UnitDef uDef : unitDefs){
            if (uDef.getName().equals(unitName))
            {
                return uDef;
            }
        }
        //outfile.println("[getUnitDef]  retorna null!!");
        return null;
    }
    
    
    
    
}
